package com.lifeng.controller;

import com.lifeng.dao.UserRepository;
import com.lifeng.domain.User;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.avg.AvgAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.avg.InternalAvg;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ResultsExtractor;
import org.springframework.data.elasticsearch.core.query.GetQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;

import static org.elasticsearch.index.query.QueryBuilders.*;

/**
 * Created by lifeng on 2019/4/26.
 * 通过spring-data-es 操作
 */
@RestController
@RequestMapping("/es")
public class EStmController {
    @Resource
    private UserRepository userRepository;
    @Resource
    private ElasticsearchTemplate elasticsearchTemplate;

    /**插入数据*/
    @RequestMapping("/add")
    @ResponseBody
    public User createindexAndMap(User user) throws Exception {
        user.setBirthday(new Date());
        return userRepository.save(user);
    }


    /**删除数据*/
    @RequestMapping("/delete")
    @ResponseBody
    public void delete(User user) throws Exception {
        userRepository.delete(user);
    }

    /**查找数据*/
    @RequestMapping("/findid")
    @ResponseBody
    public User findById(String id) throws Exception {
        return userRepository.findById(id).get();
    }

    /**查找数据*/
    @RequestMapping("/findidtm")
    @ResponseBody
    public User findByIdTm(String id) throws Exception {
        //elasticsearchTemplate 也可以操作
        GetQuery qg = new GetQuery();
        qg.setId(id);
        return elasticsearchTemplate.queryForObject(qg,User.class);
    }

    /**查找数据*/
    @RequestMapping("/findstr")
    @ResponseBody
    public Object findStr(String text,@PageableDefault Pageable pageable) throws Exception {
        //https://blog.csdn.net/tianyaleixiaowu/article/details/77965257
        //某字段(单字段)按字符串模糊查询
        //SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("remark", text)).withPageable(pageable).build();

        //单字符串模糊查询，默认排序。将从所有字段中查找包含传来的word分词后字符串的数据集
        //SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(queryStringQuery(text)).withPageable(pageable).build();

        // 最严格的匹配，属于低级查询，不进行分词的
        // term匹配，即不分词匹配，你传来什么值就会拿你传的值去做完全匹配
        //SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("id", text)).withPageable(pageable).build();

        //multi_match多个字段匹配某字符串
        //如果我们希望title，content两个字段去匹配某个字符串，只要任何一个字段包括该字符串即可
        //查询 text 在remark 或userName 包含即可 并使用分页size
        //SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(multiMatchQuery(text, "remark", "userName")).withPageable(pageable).build();

        //当我们输入“我天”时，ES会把分词后所有包含“我”和“天”的都查询出来，
        // 如果我们希望必须是包含了两个字的才能被查询出来，那么我们就需要设置一下Operator
        //如果是and的话，譬如用户输入了5个词，但包含了4个，也是显示不出来的。我们可以通过设置精度来控制。
        //SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("remark", text).operator(Operator.AND)).build();
        //SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("remark", text).operator(Operator.AND).minimumShouldMatch("75%")).build();

        //范围查询
        //SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(rangeQuery("createDate").gt("2019-01-02")).withPageable(pageable).build();

        //匹配某个字段的前缀
        //SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(prefixQuery("title",text)).withPageable(pageable).build();

        //模糊查询
        //SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(fuzzyQuery("title",text)).withPageable(pageable).build();

        /*
        合并查询
        即boolQuery，可以设置多个条件的查询方式。它的作用是用来组合多个Query，有四种方式来组合，must，mustnot，filter，should。
        must代表返回的文档必须满足must子句的条件，会参与计算分值；
        filter代表返回的文档必须满足filter子句的条件，但不会参与计算分值；
        should代表返回的文档可能满足should子句的条件，也可能不满足，有多个should时满足任何一个就可以，通过minimum_should_match设置至少满足几个。
        mustnot代表必须不满足子句的条件。
        譬如我想查询title包含“XXX”，且userId=“1”，且weight最好小于5的结果。那么就可以使用boolQuery来组合。
        */
        SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(
                boolQuery()
                   .must(termQuery("id", text))
                   .should(rangeQuery("weight").lt(3))
                   .must(matchQuery("title", text))
                   .mustNot(termQuery("id", "2"))
        ) .withFilter(matchPhraseQuery("projectId", text))
          .withHighlightFields(new HighlightBuilder.Field("title")) ////设置高亮字段
          .withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC)) //按照某个字段排序
          .withSort(SortBuilders.scoreSort().order(SortOrder.DESC)) //按相关度得分排序
          .build();

        /*
        * Elasticsearch的查询器query与过滤器filter的区别
        * 过滤器（filter）:通常用于过滤文档的范围，
        * 比如某个字段是否属于某个类型，或者是属于哪个时间区间。
        * 例如：1）创建日期是否在2014-2015年间 ；
        *      2）status字段是否为succes；
        *      3）lat_lon字段是否在某个坐标的10公里范围内？
        *
        * 查询器（query）query的使用方法像极了filter，但query更倾向于更准确的查找。
        * 例如：1）与full text search的匹配度最高 
         *     2）正则匹配 
         *     3）包含run单词，如果包含这些单词：runs、running、jog、sprint，也被视为包含run单词
         *     4）包含quick、brown、fox。这些词越接近，这份文档的相关性就越高
                查询器会计算出每份文档对于某次查询有多相关（relevant），
                然后分配文档一个相关性分数：_score。
                而这个分数会被用来对匹配了的文档进行相关性排序。
                相关性概念十分适合全文搜索（full-text search），
                这个很难能给出完整、“正确”答案的领域。
        * */
        return elasticsearchTemplate.queryForList(searchQuery,User.class);
    }

    /**
     * 聚合查询
     */
    @RequestMapping("/aggqry")
    @ResponseBody
    public Object aggQry(String text,@PageableDefault Pageable pageable) throws Exception {
        //https://blog.csdn.net/wenwen513/article/details/85163168
        //https://www.cnblogs.com/yanan7890/p/6496540.html
        //yanan7890是为聚合取的名称,false降序,true升序
        /*TermsBuilder tb= AggregationBuilders.terms("yanan7890")
                .field("需要聚合的字段").order(Terms.Order.count(false)).size(int size（默认10）); */
        //按某个字段分组
        TermsAggregationBuilder ageTerms = AggregationBuilders.terms("ageTerms").field("age");

        //求平均
        AvgAggregationBuilder ageAvg = AggregationBuilders.avg("ageAvg").field("age");

        //查询条件
        /*SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("id", text))
                .addAggregation(ageTerms)
                .addAggregation(ageAvg)
                .withPageable(pageable).build();*/

        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                //.withIndices("索引名称").withTypes("索引type") //也可以不写
                .addAggregation(ageTerms)
                .addAggregation(ageAvg)
                .build();

        Aggregations aggregations = elasticsearchTemplate.query(searchQuery,new ResultsExtractor<Aggregations>() {
            @Override
            public Aggregations extract(SearchResponse response) {
                //注意此处SearchResponse 可以 得到别的想要的数据 不仅仅是 Aggregations
                return response.getAggregations();
            }
        });
        // 获取分组聚合结果
        LongTerms longTerms = (LongTerms) aggregations.asMap().get("ageTerms");
        List<LongTerms.Bucket> bucketList = longTerms.getBuckets();
        for(LongTerms.Bucket bucket:bucketList){
            //聚合字段的相应名称
            String key = bucket.getKeyAsString();
            //相应聚合结果
            Long count = bucket.getDocCount();
            System.out.println(bucket);
        }

        // 获取平均数结果
        InternalAvg internalAgeAvg = (InternalAvg) aggregations.asMap().get("ageAvg");
        Double value = internalAgeAvg.getValue();
        String name = internalAgeAvg.getName();
        return null;
    }
}
